/*
 * Open Source Physics software is free software as described near the bottom of this code file.
 *
 * For additional information and documentation on Open Source Physics please see:
 * <http://www.opensourcephysics.org/>
 */

package org.opensourcephysics.tools;

import java.io.File;
import java.rmi.*;
import java.awt.*;
import java.awt.event.*;

import javax.swing.*;

import org.opensourcephysics.controls.*;
import org.opensourcephysics.display.OSPRuntime;

/**
 * This provides a GUI for encrypting and decrypting XMLControls.
 *
 * @author Douglas Brown
 * @version 1.0
 */
public class EncryptionTool extends JFrame implements Tool {
  // static constants
	private static final String VERSION = " 1.0"; //$NON-NLS-1$

  // static fields
	private static Dimension dim = new Dimension(720, 500);

  // instance fields
	private XMLTreePanel treePanel;
	private JPanel contentPane = new JPanel(new BorderLayout());
	private JobManager jobManager = new JobManager(this);
	private JTextField passwordField;
	private JCheckBox encryptedCheckBox;
	private JCheckBox previewCheckBox;
	private String fileName;
	private JMenuItem openItem;
	private JMenuItem saveItem;
	private JMenuItem saveAsItem;
	private JLabel passwordLabel;
	private JMenu fileMenu;
	private JMenu helpMenu;
	private JMenuItem exitItem;
	private JMenuItem logItem;
	private JMenuItem aboutItem;
	private Icon openIcon;
	private JButton openButton;
	private Icon saveIcon;
	private JButton saveButton;

  /**
   * A shared encryption tool.
   */
  private static final EncryptionTool ENCRYPTION_TOOL = new EncryptionTool();

  /**
   * Gets the shared EncryptionTool.
   *
   * @return the shared EncryptionTool
   */
  public static EncryptionTool getTool() {
    return ENCRYPTION_TOOL ;
  }

  /**
   * Constructs a blank EncryptionTool.
   */
  public EncryptionTool() {
    String name = "EncryptionTool"; //$NON-NLS-1$
    setName(name);
    createGUI();
    refreshGUI();
    Toolbox.addTool(name, this);
    // Toolbox.addRMITool(name, this);
  }

  /**
   * Constructs a EncryptionTool and opens the specified xml file.
   *
   * @param fileName the name of the xml file
   */
  public EncryptionTool(String fileName) {
    this();
    open(fileName);
  }

  /**
   * Opens an xml file specified by name.
   *
   * @param fileName the file name
   * @return the file name, if successfully opened
   */
  public String open(String fileName) {
    OSPLog.fine("opening "+fileName); //$NON-NLS-1$
    // read the file into an XML control
    XMLControlElement control = new XMLControlElement();
    control.setDecryptPolicy(XMLControlElement.NEVER_DECRYPT);
    control.read(fileName);
    if (control.failedToRead()) return null;
    String pass = control.getPassword();
    if (pass == null) {
    	passwordField.setText(null);
      displayXML(control);
    	encryptedCheckBox.setEnabled(true);
    }
    else if (passwordField.getText().equals(pass)) {
    	displayXML(decrypt(control));
    	encryptedCheckBox.setEnabled(true);
    }
    else {
    	displayXML(control);
    	encryptedCheckBox.setEnabled(false);
    }
    this.fileName = fileName;
    refreshGUI();
    return fileName;
  }

  /**
   * Sends a job to this tool and specifies a tool to reply to.
   *
   * @param job the Job
   * @param replyTo the tool to notify when the job is complete (may be null)
   * @throws RemoteException
   */
  public void send(Job job, Tool replyTo) throws RemoteException {
  	// read xml into XMLControl and display the control
    XMLControlElement control = new XMLControlElement();
    control.setDecryptPolicy(XMLControlElement.NEVER_DECRYPT);
    control.readXML(job.getXML());
    if (control.failedToRead()) return;
    String pass = control.getPassword();
    if (pass == null) {
    	passwordField.setText(null);
      displayXML(control);
    }
    else if (passwordField.getText().equals(pass)) {
    	displayXML(decrypt(control));
    }
    else displayXML(control);
    fileName = null;
    refreshGUI();
    // log the job with the job manager
    jobManager.log(job, replyTo);
  }

  /**
   * Refreshes the GUI.
   */
  public void refreshGUI() {
  	String title = ToolsRes.getString("EncryptionTool.Title"); //$NON-NLS-1$
  	if (fileName != null) title += ": " + fileName; //$NON-NLS-1$
    setTitle(title);
    openButton.setToolTipText(ToolsRes.getString(
						"EncryptionTool.Button.Open.ToolTip")); //$NON-NLS-1$
    saveButton.setToolTipText(ToolsRes.getString(
						"EncryptionTool.Button.Save.ToolTip")); //$NON-NLS-1$
    passwordLabel.setText(ToolsRes.getString(
						"EncryptionTool.Label.Password")); //$NON-NLS-1$
    passwordField.setToolTipText(ToolsRes.getString(
						"EncryptionTool.PasswordField.ToolTip")); //$NON-NLS-1$
    encryptedCheckBox.setText(ToolsRes.getString(
    				"EncryptionTool.CheckBox.Encrypted")); //$NON-NLS-1$
    encryptedCheckBox.setToolTipText(ToolsRes.getString(
						"EncryptionTool.CheckBox.Encrypted.ToolTip")); //$NON-NLS-1$
    previewCheckBox.setText(ToolsRes.getString(
						"EncryptionTool.CheckBox.Preview")); //$NON-NLS-1$
    previewCheckBox.setToolTipText(ToolsRes.getString(
						"EncryptionTool.CheckBox.Preview.ToolTip")); //$NON-NLS-1$
    fileMenu.setText(ToolsRes.getString(
    				"EncryptionTool.Menu.File")); //$NON-NLS-1$
    openItem.setText(ToolsRes.getString(
    				"EncryptionTool.MenuItem.Open")); //$NON-NLS-1$
    saveItem.setText(ToolsRes.getString(
    				"EncryptionTool.MenuItem.Save")); //$NON-NLS-1$
    saveAsItem.setText(ToolsRes.getString(
    				"EncryptionTool.MenuItem.SaveAs")); //$NON-NLS-1$
    exitItem.setText(ToolsRes.getString(
    				"EncryptionTool.MenuItem.Exit")); //$NON-NLS-1$
    helpMenu.setText(ToolsRes.getString(
    				"EncryptionTool.Menu.Help")); //$NON-NLS-1$
    logItem.setText(ToolsRes.getString(
    				"EncryptionTool.MenuItem.Log")); //$NON-NLS-1$
    aboutItem.setText(ToolsRes.getString(
    				"EncryptionTool.MenuItem.About")); //$NON-NLS-1$
    saveButton.setEnabled(encryptedCheckBox.isEnabled());
    saveItem.setEnabled(encryptedCheckBox.isEnabled());
    saveAsItem.setEnabled(encryptedCheckBox.isEnabled());
    XMLControlElement control = getCurrentControl();
    encryptedCheckBox.setSelected(control != null &&
    				control.getPassword() != null);
    passwordLabel.setEnabled(encryptedCheckBox.isSelected());
    passwordField.setEnabled(encryptedCheckBox.isSelected());
    previewCheckBox.setEnabled(encryptedCheckBox.isEnabled() &&
    				encryptedCheckBox.isSelected());
    previewCheckBox.setSelected(control != null &&
    				control.getObjectClass() == Cryptic.class);
  }

  /**
   * Main entry point when used as application.
   *
   * @param args ignored
   */
  public static void main(String[] args) {
  	EncryptionTool tool = getTool();
  	tool.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
  	tool.open("Untitled.xset"); //$NON-NLS-1$
    tool.setVisible(true);
  }

// ______________________________ private methods _____________________________

  /**
   * Gets the currently displayed XMLControlElement.
   *
   * @return the XMLControlElement
   */
  private XMLControlElement getCurrentControl() {
  	if (treePanel == null) return null;
  	XMLControl control = treePanel.getControl();
  	if (control instanceof XMLControlElement)
  		return (XMLControlElement)control;
  	return null;
  }



  /**
   * Displays an XMLControlElement.
   *
   * @param control the XMLControlElement
   */
  private void displayXML(XMLControlElement control) {
  	if (treePanel != null) contentPane.remove(treePanel);
    treePanel = new XMLTreePanel(control, false);
    contentPane.add(treePanel, BorderLayout.CENTER);
    validate();
//    treePanel.setSelectedNode("xml_password"); //$NON-NLS-1$
    refreshGUI();
  }

  /**
   * Sets the password of the currently displayed control.
   *
   * @param password the password
   */
  private void setPassword(String password) {
  	XMLControlElement control = getCurrentControl();
  	if (control == null) return;
		String pass = control.getPassword();
  	if (!encryptedCheckBox.isEnabled()) {
    	boolean verified = password.equals(pass);
  	  if (verified) {
  	  	displayXML(decrypt(control));
  	  	encryptedCheckBox.setEnabled(true);  	  }
  	  else {
  	  	Toolkit.getDefaultToolkit().beep();
        OSPLog.fine("Bad password: "+password); //$NON-NLS-1$
  	  }
  	}
  	else if (control.getObjectClass() == Cryptic.class) {
  		// decrypt control, change password, and re-encrypt
  		XMLControlElement temp = decrypt(control);
  		temp.setPassword(password);
  		temp = encrypt(temp);
  		control.setValue("cryptic", temp.getString("cryptic")); //$NON-NLS-1$ //$NON-NLS-2$
  		treePanel.refresh();
  	}
  	else {
  		// change password
  		if (password.equals("") && !encryptedCheckBox.isSelected()) { //$NON-NLS-1$
  			password = null;
  		}
  		control.setPassword(password);
  		treePanel.refresh();
//      treePanel.setSelectedNode("xml_password"); //$NON-NLS-1$
  	}
  	refreshGUI();
  }

  /**
   * Encrypts the specified XMLcontrol using a Cryptic object.
   *
   * @param control the XMLControl
   * @return the encrypted control
   */
  private XMLControlElement encrypt(XMLControlElement control) {
  	// return controls that are already encrypted
  	if (control.getObjectClass() == Cryptic.class) return control;
    // encrypt control and set its password
    String xml = control.toXML();
    Cryptic cryptic = new Cryptic(xml);
    XMLControlElement encrypted = new XMLControlElement(cryptic);
    encrypted.setPassword(control.getPassword());
    return encrypted;
  }

  /**
   * Decrypts the specified XMLcontrol.
   *
   * @param control the XMLControl
   * @return the decrypted control
   */
  private XMLControlElement decrypt(XMLControlElement control) {
  	if (control.getObjectClass() != Cryptic.class) return control;
  	Cryptic cryptic = (Cryptic)control.loadObject(null);
  	// get the decrypted xml, make new control and set password
  	String xml = cryptic.decrypt();
    XMLControlElement decrypted = new XMLControlElement(xml);
    return decrypted;
  }

  /**
   * Opens an xml file selected with a chooser.
   *
   * @return the name of the opened file, or null if none opened
   */
  private String open() {
    int result = OSPRuntime.getChooser().showOpenDialog(this);
    if(result==JFileChooser.APPROVE_OPTION) {
      OSPRuntime.chooserDir = OSPRuntime.getChooser().getCurrentDirectory().toString();
      String fileName = OSPRuntime.getChooser().getSelectedFile().getAbsolutePath();
      fileName = XML.getRelativePath(fileName);
      return open(fileName);
    }
    return null;
  }

  /**
   * Saves the current xml control to the specified file.
   *
   * @param fileName the file name
   * @return the name of the saved file, or null if not saved
   */
  private String save(String fileName) {
  	if (fileName == null || fileName.equals("")) return null; //$NON-NLS-1$
  	if (passwordField.getBackground() == Color.yellow) {
  		passwordField.setBackground(Color.white);
  		setPassword(passwordField.getText());
  	}
    XMLControlElement control = getCurrentControl();
    if (control == null) return null;
  	if (control.getObjectClass() == Cryptic.class)
  		control = decrypt(control);
    if (control.write(fileName) == null) return null;
    this.fileName = fileName;
    refreshGUI();
    return fileName;
  }

  /**
   * Saves the currently displayed xml control to a file selected with a chooser.
   *
   * @return the name of the saved file, or null if not saved
   */
  private String saveAs() {
    int result = OSPRuntime.getChooser().showSaveDialog(this);
    if(result==JFileChooser.APPROVE_OPTION) {
      OSPRuntime.chooserDir = OSPRuntime.getChooser().getCurrentDirectory().toString();
      File file = OSPRuntime.getChooser().getSelectedFile();
      // check to see if file already exists
      if(file.exists()) {
        int selected = JOptionPane.showConfirmDialog(null,
        				ToolsRes.getString("EncryptionTool.Dialog.ReplaceFile.Message")+file.getName()+"?",  //$NON-NLS-1$ //$NON-NLS-2$
        				ToolsRes.getString("EncryptionTool.Dialog.ReplaceFile.Title"),  //$NON-NLS-1$
        				JOptionPane.YES_NO_CANCEL_OPTION);
        if(selected!=JOptionPane.YES_OPTION) {
          return null;
        }
      }
      String fileName = file.getAbsolutePath();
      if(fileName==null||fileName.trim().equals("")) { //$NON-NLS-1$
        return null;
      }
      return save(XML.getRelativePath(fileName));
    }
    return null;
  }

  /**
   * Creates the GUI.
   */
  private void createGUI() {
    // configure the frame
    contentPane.setPreferredSize(dim);
    setContentPane(contentPane);
    setDefaultCloseOperation(WindowConstants.HIDE_ON_CLOSE);
    JToolBar toolbar = new JToolBar();
    toolbar.setFloatable(false);
    contentPane.add(toolbar, BorderLayout.NORTH);
    String imageFile = "/org/opensourcephysics/resources/tools/images/open.gif"; //$NON-NLS-1$
    openIcon = ResourceLoader.getIcon(imageFile);
    openButton = new JButton(openIcon);
    openButton.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent e) {
        open();
      }
    });
    toolbar.add(openButton);
    imageFile = "/org/opensourcephysics/resources/tools/images/save.gif"; //$NON-NLS-1$
    saveIcon = ResourceLoader.getIcon(imageFile);
    saveButton = new JButton(saveIcon);
    saveButton.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent e) {
      	save(fileName);
      }
    });
    toolbar.add(saveButton);
    toolbar.addSeparator();
    passwordLabel = new JLabel();
    passwordLabel.setBorder(BorderFactory.createEmptyBorder(0, 3, 0, 3));
    passwordField = new JTextField(20);
    passwordField.addKeyListener(new KeyAdapter() {
      public void keyPressed(KeyEvent e) {
        if(e.getKeyCode()==KeyEvent.VK_ENTER) {
        	passwordField.setBackground(Color.white);
      		setPassword(passwordField.getText());
        } else if (e.getKeyChar() != KeyEvent.CHAR_UNDEFINED) {
        	passwordField.setBackground(Color.yellow);
        }
      }
    });
    passwordField.addFocusListener(new FocusAdapter() {
      public void focusLost(FocusEvent e) {
      	if (passwordField.getBackground() == Color.yellow) {
      		passwordField.setBackground(Color.white);
      		setPassword(passwordField.getText());
      	}
      }
    });
    toolbar.add(passwordLabel);
    toolbar.add(passwordField);
    encryptedCheckBox = new JCheckBox(""); //$NON-NLS-1$
    encryptedCheckBox.setEnabled(false);
    encryptedCheckBox.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent e) {
      	// if selected, set password to current passwordField text
      	if (encryptedCheckBox.isSelected()) {
      		setPassword(passwordField.getText());
      	}
      	// else decrypt and set password to ""
      	else {
        	XMLControlElement control = getCurrentControl();
        	if (control.getObjectClass() == Cryptic.class) {
        		control = decrypt(control);
        		control.setPassword(null);
      	    displayXML(control);
        	}
    	    setPassword(""); //$NON-NLS-1$
      	}
      }
    });
    encryptedCheckBox.setContentAreaFilled(false);
    toolbar.add(encryptedCheckBox);
    previewCheckBox = new JCheckBox(""); //$NON-NLS-1$
    previewCheckBox.setOpaque(false);
    previewCheckBox.setEnabled(false);
    previewCheckBox.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent e) {
      	XMLControlElement control = getCurrentControl();
        if (previewCheckBox.isSelected()) {
          displayXML(encrypt(control));
        }
        else {
          displayXML(decrypt(control));
        }
      }
    });
    toolbar.add(previewCheckBox);
    // create the menu bar
    int keyMask = Toolkit.getDefaultToolkit().getMenuShortcutKeyMask();
    JMenuBar menubar = new JMenuBar();
    // file menu
    fileMenu = new JMenu();
    menubar.add(fileMenu);
    // open item
    openItem = new JMenuItem();
    openItem.setAccelerator(KeyStroke.getKeyStroke('O', keyMask));
    openItem.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent e) {
        open();
      }
    });
    fileMenu.add(openItem);
    fileMenu.addSeparator();
    // save item
    saveItem = new JMenuItem();
    saveItem.setAccelerator(KeyStroke.getKeyStroke('S', keyMask));
    saveItem.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent e) {
      	save(fileName);
      }
    });
    saveItem.setEnabled(false);
    fileMenu.add(saveItem);
    // save as item
    saveAsItem = new JMenuItem();
    saveAsItem.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent e) {
      	saveAs();
      }
    });
    saveAsItem.setEnabled(false);
    fileMenu.add(saveAsItem);
    exitItem = new JMenuItem(ToolsRes.getString("MenuItem.Exit")); //$NON-NLS-1$
    exitItem.setAccelerator(KeyStroke.getKeyStroke('Q', keyMask));
    exitItem.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent e) {
        System.exit(0);
      }
    });
    fileMenu.addSeparator();
    fileMenu.add(exitItem);
    // help menu
    helpMenu = new JMenu();
    menubar.add(helpMenu);
    // log item
    logItem = new JMenuItem();
    logItem.setAccelerator(KeyStroke.getKeyStroke('L', keyMask));
    logItem.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent e) {
      	OSPLog log = OSPLog.getOSPLog();
        if(log.getLocation().x==0&&log.getLocation().y==0) {
          Point p = getLocation();
          log.setLocation(p.x+28, p.y+28);
        }
        OSPLog.showLog();
      }
    });
    helpMenu.add(logItem);
    helpMenu.addSeparator();
    // about item
    aboutItem = new JMenuItem();
    aboutItem.setAccelerator(KeyStroke.getKeyStroke('A', keyMask));
    aboutItem.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent e) {
      	String toolname = ToolsRes.getString("EncryptionTool.About.ToolName"); //$NON-NLS-1$
        String aboutString = toolname + VERSION + XML.NEW_LINE
        	+ToolsRes.getString("EncryptionTool.About.OSPName") + XML.NEW_LINE //$NON-NLS-1$
        	+"www.opensourcephysics.org"; //$NON-NLS-1$
        JOptionPane.showMessageDialog(EncryptionTool.this,
        				aboutString,
								ToolsRes.getString("EncryptionTool.About.Title"), //$NON-NLS-1$
								JOptionPane.INFORMATION_MESSAGE);
      }
    });
    helpMenu.add(aboutItem);
    setJMenuBar(menubar);
    pack();
    // center this on the screen
    Dimension dim = Toolkit.getDefaultToolkit().getScreenSize();
    int x = (dim.width-getBounds().width)/2;
    int y = (dim.height-getBounds().height)/2;
    setLocation(x, y);
  }
}

/*
 * Open Source Physics software is free software; you can redistribute
 * it and/or modify it under the terms of the GNU General Public License (GPL) as
 * published by the Free Software Foundation; either version 2 of the License,
 * or(at your option) any later version.

 * Code that uses any portion of the code in the org.opensourcephysics package
 * or any subpackage (subdirectory) of this package must must also be be released
 * under the GNU GPL license.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston MA 02111-1307 USA
 * or view the license online at http://www.gnu.org/copyleft/gpl.html
 *
 * Copyright (c) 2007  The Open Source Physics project
 *                     http://www.opensourcephysics.org
 */
